
#include "gpx_parser.h"

#include <fstream>
#include "glog/logging.h"
#include "date_time.h"
#include "expat_parser.h"
#include "gps.pb.h"

namespace gps_proto {

GpxParser::GpxParser(GpsProto* gps_proto)
  : GpsParser(gps_proto) {
}

// virtual
void GpxParser::StartElement(const char *name, const char **atts) {
  VLOG(2) << "StartElement " << name;
  if (strcmp("wpt", name) == 0) {
    if (!gps_proto_) {
      VLOG(1) << "ignoring wpt outside of gpx";
      return;
    }
    point_proto_ = gps_proto_->add_waypoint();
    ParseLatLonAttrs(atts, point_proto_);
  } else if (strcmp("trk", name) == 0) {
    if (!gps_proto_) {
      VLOG(1) << "ignoring trk outside of gpx";
      return;
    }
    track_proto_ = gps_proto_->add_track();
  } else if (strcmp("trkseg", name) == 0) {
    if (!track_proto_) {
      VLOG(1) << "ignoring trkseg outside of trk";
      return;
    }
    segment_proto_ = track_proto_->add_segment();
  } else if (strcmp("trkpt", name) == 0) {
    if (!segment_proto_) {
      VLOG(1) << "ignoring trkpt outside of trkseg";
      return;
    }
    // XXX don't create the trkpt until we have a good lat,lon
    // <trkpt lat="-33.911973070" lon="18.422974152">
    point_proto_ = segment_proto_->add_point();
    ParseLatLonAttrs(atts, point_proto_);
  } else if (strcmp("ele", name) == 0 ||
             strcmp("name", name) == 0 ||
             strcmp("time", name) == 0) {
    // <ele>4.943848</ele>
    // <name>SARATOGA</name>
    // <time>2008-10-11T14:55:41Z</time>
    GatherCharData();
  }
}

// virtual void
void GpxParser::EndElement(const char *name) {
  if (strcmp("trk", name) == 0) {
    track_proto_ = NULL;
  } else if (strcmp("trkseg", name) == 0) {
    //DiffEncodeTrkSeg(segment_proto_);
    segment_proto_ = NULL;
  } else if (strcmp("trkpt", name) == 0) {
    point_proto_ = NULL;
  } else if (strcmp("wpt", name) == 0) {
    point_proto_ = NULL;
  } else if (strcmp("ele", name) == 0) {
    if (!point_proto_) {
      VLOG(1) << "ignoring ele outside of trkpt";
      return;
    }
    // <ele>4.943848</ele>
    point_proto_->set_ele(strtod(GetCharData().c_str(), NULL));
  } else if (strcmp("name", name) == 0) {
    if (!point_proto_) {
      VLOG(1) << "ignoring name outside of trkpt";
      return;
    }
    point_proto_->set_name(GetCharData());
  } else if (strcmp("time", name) == 0) {
    if (!point_proto_) {
      VLOG(1) << "ignoring time outside of trkpt";
      return;
    }
    // <time>2008-10-11T14:55:41Z</time>
    //   or
    // <time>2008-10-11T14:55:41-00:00</time>  (Greenwhich)
    // <time>2008-10-11T14:55:41-08:00</time>  (California)
    // TODO: <time>2005-11-30T21:37:10.000Z</time>
    point_proto_->set_time(DateTimeToTimeT(GetCharData()));
  } else if (strcmp("name", name) == 0) {
    point_proto_->set_name(GetCharData());
  }
}

// static
bool GpxParser::ParseFromString(const std::string& gpx, GpsProto* gps_proto) {
  GpxParser gpx_parser(gps_proto);
  return ExpatParser::ParseString(gpx, &gpx_parser, NULL, false);
}

// static
bool GpxParser::ParseFromIstream(std::istream* input, GpsProto* gps_proto) {

  if (!input || !gps_proto) {
    return false;  // No input or no output?  Not much to do.
  }
  // Create a GpsParser instance which will write to the user's gps_proto.
  GpxParser gpx_parser(gps_proto);
  // Create an ExpatParser using the (non-namespace-aware) ExpatHandler for GPX.
  ExpatParser expat_parser(&gpx_parser, false);
  // Use the ExpatParser's streamed parsing API on the input.
  // (This loop is taken from libkml's src/kml/engine/kml_stream.cc)
  const size_t kBufSize(4096);
  while (input->good()) {
    // Ask expat for a buffer to write into.
    void* buf = expat_parser.GetInternalBuffer(kBufSize);
    if (!buf) {
      return false;  // Memory allocation error.
    }
    // Read a buffer's worth from the input stream.
    std::streamsize read_size =
        input->read(static_cast<char*>(buf), kBufSize).gcount();
    // Parse that buffer.
    if (!expat_parser.ParseInternalBuffer(read_size, NULL, input->eof())) {
      return false;  // Parse error.
    }
  }
  return true;
}

// static
bool GpxParser::ParseFromFile(const char* filename, GpsProto* gps_proto) {
  std::ifstream input_gpx(filename,
                          std::ios_base::in|std::ios_base::binary);
  if (!input_gpx.is_open() || !input_gpx.good()) {
    LOG(ERROR) << "open failed: " << filename;
    return false;
  }
  return ParseFromIstream(&input_gpx, gps_proto);
}

}  // end namespace gps_proto
